package com.moonshineproject.plugin.templating
{
	import __AS3__.vec.Vector;
	
	import com.moonshineproject.editor.BasicTextEditor;
	import com.moonshineproject.events.AddTabEvent;
	import com.moonshineproject.events.EditorPluginEvent;
	import com.moonshineproject.events.NewProjectEvent;
	import com.moonshineproject.events.OpenFileEvent;
	import com.moonshineproject.plugin.IMenuPlugin;
	import com.moonshineproject.plugin.PluginBase;
	import com.moonshineproject.plugin.menu.vo.MenuItem;
	import com.moonshineproject.plugin.project.event.ProjectEvent;
	import com.moonshineproject.plugin.project.vo.ProjectVO;
	import com.moonshineproject.plugin.settings.ISettingsProvider;
	import com.moonshineproject.plugin.settings.vo.ISetting;
	import com.moonshineproject.plugin.settings.vo.StaticLabelSetting;
	import com.moonshineproject.plugin.templating.event.RequestTemplatesEvent;
	import com.moonshineproject.plugin.templating.event.TemplateEvent;
	import com.moonshineproject.plugin.templating.settings.NewTemplateSetting;
	import com.moonshineproject.plugin.templating.settings.TemplateSetting;
	import com.moonshineproject.plugin.templating.settings.renderer.NewTemplateRenderer;
	import com.moonshineproject.plugin.templating.settings.renderer.TemplateRenderer;
	import com.moonshineproject.utils.FileUtil;
	
	import flash.events.Event;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;

	/*
		Templating plugin
		
		Provides templates & possibility to customize them
		
		Standard templates ship in the app-dir, but since we can't change those files once installed 
		we override them by copying them to app-storage-dir & let the user modify them there.
	*/
	
	public class TemplatingPlugin extends PluginBase implements ISettingsProvider, IMenuPlugin
	{
		override public function get name():String 			{return "Templating Plugin";}
		override public function get author():String 		{return "Moonshine Project Team";}
		override public function get description():String 	{return "Provides templating services. Changes made here are instantly applied.";}
		
		protected var projectTemplates:Array = [];
		protected var fileTemplates:Array = [];
		
		protected var templatesDir:File;
		protected var customTemplatesDir:File;
		
		protected var settingsList:Vector.<ISetting>;
		protected var newFileTemplateSetting:NewTemplateSetting;
		protected var newProjectTemplateSetting:NewTemplateSetting;
		
		public function TemplatingPlugin()
		{
			super();
			
			templatesDir = File.applicationDirectory.resolvePath("assets/templates");
			customTemplatesDir = File.applicationStorageDirectory.resolvePath("templates");
			
			readTemplates();
		}
		
		override public function activate():void
		{
			super.activate();
			
			dispatcher.addEventListener(RequestTemplatesEvent.EVENT_REQUEST_TEMPLATES, handleTemplateRequest);
			dispatcher.addEventListener(TemplateEvent.CREATE_NEW_FILE, handleCreateFileTemplate);
		}
		
		protected function readTemplates():void
		{
			// Find default templates
			var files:File = templatesDir.resolvePath("files");
			var list:Array = files.getDirectoryListing();
			for each (var file:File in list)
			{
				if (!file.isHidden)
					fileTemplates.push(file);	
			}
			
			var projects:File = templatesDir.resolvePath("projects");
			list = projects.getDirectoryListing();
			for each (file in list)
			{
				if (!file.isHidden)
					projectTemplates.push(file);
			}
				
			// Find user-added custom templates
			if (!customTemplatesDir.exists) customTemplatesDir.createDirectory();
			
			files = customTemplatesDir.resolvePath("files");
			if (!files.exists) files.createDirectory();
			var fileList:Array = files.getDirectoryListing();
			
			for each (file in fileList)
			{
				if (getOriginalFileForCustom(file).exists == false
					&& !file.isHidden)
				{
					fileTemplates.push(file);
				}
			}
			
			projects = customTemplatesDir.resolvePath("projects");
			if (!projects.exists) projects.createDirectory();
			var projectList:Array = projects.getDirectoryListing();
			
			for each (file in projectList)
			{
				if (getOriginalFileForCustom(file).exists == false
					&& !file.isHidden)
				{
					projectTemplates.push(file);
				}
			}
		}
		
		public function getSettingsList():Vector.<ISetting>	
		{	
			// Build settings on each template (just a File object pointing to a directory)
			//  requires good names for the directories, but shouldn't be a problem
			var settings:Vector.<ISetting> = new Vector.<ISetting>();
			
			var fileLabel:StaticLabelSetting = new StaticLabelSetting("Files", 14);
			settings.push(fileLabel);
			
			var setting:TemplateSetting;
			for each (var t:File in fileTemplates)
			{
				if (t.isHidden) continue;
				
				setting = getTemplateSetting(t);
				settings.push(setting);
			}
			
			newFileTemplateSetting = new NewTemplateSetting("Add file template");
			newFileTemplateSetting.renderer.addEventListener('create', handleFileTemplateCreate);
			
			settings.push(newFileTemplateSetting);
			
			var projectLabel:StaticLabelSetting = new StaticLabelSetting("Projects", 14);
			settings.push(projectLabel);
			
			for each (var p:File in projectTemplates)
			{
				if (p.isHidden) continue;

				setting = getTemplateSetting(p);				
				settings.push(setting);
			}
			
			newProjectTemplateSetting = new NewTemplateSetting("Add project template");
			newProjectTemplateSetting.renderer.addEventListener('create', handleProjectTemplateCreate);
			
			settings.push(newProjectTemplateSetting);
			
			settingsList = settings;
			return settings;
		}
		
		public function getMenu():MenuItem
		{	
			var newFileMenu:MenuItem = new MenuItem('New');
			newFileMenu.parents = ["File", "New"];
			newFileMenu.items = new Vector.<MenuItem>();
			
			for each (var fileTemplate:File in fileTemplates)
			{
				if (fileTemplate.isHidden) continue;
				var lbl:String = TemplatingHelper.getTemplateLabel(fileTemplate);
				
				// TODO: Do MenuEvent and have data:* for this kind of thing
				var eventType:String = "eventNewFileFromTemplate"+lbl;
				
				dispatcher.addEventListener(eventType, handleNewTemplateFile);
				
				var menuItem:MenuItem = new MenuItem(lbl, null, eventType);
				menuItem.data = fileTemplate; 
				
				newFileMenu.items.push(menuItem);
			}
			
			var separator:MenuItem = new MenuItem(null);
			newFileMenu.items.push(separator);
			
			for each (var projectTemplate:File in projectTemplates)
			{
				if (projectTemplate.isHidden) continue;
				lbl = TemplatingHelper.getTemplateLabel(projectTemplate);
				
				eventType = "eventNewProjectFromTemplate"+lbl;
				
				dispatcher.addEventListener(eventType, handleNewProjectFile)
				
				menuItem = new MenuItem(lbl, null, eventType);
				menuItem.data = projectTemplate;
				
				newFileMenu.items.push(menuItem);	
			}
			
			return newFileMenu;
		}
		
		protected function getTemplateSetting(template:File):TemplateSetting
		{
			var originalTemplate:File;
			var customTemplate:File;
			
			if (isCustom(template))
			{
				originalTemplate = null;
				customTemplate = template;
			}
			else
			{
				originalTemplate = template;
				customTemplate = getCustomFileFor(template);
			}
			
			var setting:TemplateSetting = new TemplateSetting(originalTemplate, customTemplate, template.name);
			setting.renderer.addEventListener(TemplateRenderer.EVENT_MODIFY, handleTemplateModify, false, 0, true);
			setting.renderer.addEventListener(TemplateRenderer.EVENT_RESET, handleTemplateReset, false, 0, true);
			setting.renderer.addEventListener(TemplateRenderer.EVENT_REMOVE, handleTemplateReset, false, 0, true);
			
			return setting;
		}
		
		
		protected function handleFileTemplateCreate(event:Event):void
		{
			// Create new file
			var newTemplate:File = this.customTemplatesDir.resolvePath("files/New file template.txt");
			
			FileUtil.createFile(newTemplate);
			
			// Add setting for it so we can remove it
			var t:TemplateSetting = new TemplateSetting(null, newTemplate, newTemplate.name);
			t.renderer.addEventListener(TemplateRenderer.EVENT_MODIFY, handleTemplateModify);
			t.renderer.addEventListener(TemplateRenderer.EVENT_REMOVE, handleTemplateReset);
			var newPos:int = this.settingsList.indexOf(newFileTemplateSetting);
			settingsList.splice(newPos, 0, t);
			
			// Force settings view to redraw
			NewTemplateRenderer(event.target).dispatchEvent(new Event('refresh'));
			
			// Add to project view so user can rename it
			dispatcher.dispatchEvent(
				new ProjectEvent(ProjectEvent.ADD_PROJECT, new ProjectVO(newTemplate))
			);
						
			
			// Update internal template list
			readTemplates();
		}
		
		protected function handleProjectTemplateCreate(event:Event):void
		{
			var newTemplate:File = this.customTemplatesDir.resolvePath("projects/New Project Template/");
			newTemplate.createDirectory();
			
			var t:TemplateSetting = new TemplateSetting(null, newTemplate, newTemplate.name);
			t.renderer.addEventListener(TemplateRenderer.EVENT_MODIFY, handleTemplateModify);
			t.renderer.addEventListener(TemplateRenderer.EVENT_REMOVE, handleTemplateReset);
			var newPos:int = this.settingsList.indexOf(newProjectTemplateSetting);
			settingsList.splice(newPos, 0, t);
			
			dispatcher.dispatchEvent(
				new ProjectEvent(ProjectEvent.ADD_PROJECT, new ProjectVO(newTemplate))
			);
			
			NewTemplateRenderer(event.target).dispatchEvent(new Event('refresh'));
			
			readTemplates();
		}
		
		protected function handleTemplateModify(event:Event):void
		{
			var rdr:TemplateRenderer = TemplateRenderer(event.target);
			var original:File = rdr.setting.originalTemplate;
			var custom:File = rdr.setting.customTemplate;
			
			var p:ProjectVO;
			
			if (!original || !original.exists)
			{
				p = new ProjectVO(custom)
			}
			else if (!custom.exists)
			{
				// Copy to app-storage so we can edit
				original.copyTo(custom, true);
				p = new ProjectVO(original);
			}
			
			// If project or custom, show in Project View so user can rename it
			if (custom.isDirectory || !original || !original.exists)
			{
				dispatcher.dispatchEvent(
					new ProjectEvent(ProjectEvent.ADD_PROJECT, p)
				);
			}
			
			// If not a project, open the template for editing
			if (!custom.isDirectory)
			{
				dispatcher.dispatchEvent(
					new OpenFileEvent(OpenFileEvent.OPEN_FILE, custom)
				);
			}
		}
		
		
		
		protected function getCustomFileFor(template:File):File
		{
			var appDirPath:String = File.applicationDirectory.nativePath;
			var appStorageDirPath:String = File.applicationStorageDirectory.nativePath;

			var customTemplatePath:String = template.nativePath.substr(appDirPath.length+1);
			var customTemplate:File = File.applicationStorageDirectory.resolvePath(customTemplatePath);
			
			return customTemplate;
		}
		
		protected function getOriginalFileForCustom(template:File):File
		{
			var appDirPath:String = File.applicationDirectory.nativePath;
			var appStorageDirPath:String = File.applicationStorageDirectory.nativePath;

			var originalTemplatePath:String = template.nativePath.substr(appStorageDirPath.length+1);
			var originalTemplate:File = File.applicationDirectory.resolvePath(originalTemplatePath);
			
			return originalTemplate;
		}
		
		protected function handleTemplateReset(event:Event):void
		{
			// Resetting a template just removes it from app-storage
			var rdr:TemplateRenderer = TemplateRenderer(event.target);
			var original:File = rdr.setting.originalTemplate;
			var custom:File = rdr.setting.customTemplate;
			
			if (custom.isDirectory) custom.deleteDirectory(true);
			else custom.deleteFile();
			
			if (!original)
			{
				var idx:int = settingsList.indexOf(rdr.setting);
				settingsList.splice(idx, 1);
				rdr.dispatchEvent(new Event('refresh'));
				
				readTemplates();	
			}
		}
		
		
		protected function handleCreateFileTemplate(event:TemplateEvent):void
		{
			// If we know where to place it we replace strings inside it
			if (event.location)
			{
				// Request additional data for templating
				var event:TemplateEvent = new TemplateEvent(TemplateEvent.REQUEST_ADDITIONAL_DATA, event.template, event.location);
				dispatcher.dispatchEvent(event);
								
				var helper:TemplatingHelper = new TemplatingHelper();
				helper.templatingData = event.templatingData;
				helper.fileTemplate(event.template, event.location);
				
				dispatcher.dispatchEvent(
					new OpenFileEvent(OpenFileEvent.OPEN_FILE, event.location)
				);
			}
			else
			{
				// Otherwise we just create the file
				createFile(event.template);
			}
		}
		
		protected function handleNewTemplateFile(event:Event):void
		{
			var eventName:String = event.type.substr(24);
			
			// Figure out which menu item was clicked (add extra data var to MenuPlugin/event dispatching?)
			for (var i:int = 0; i < fileTemplates.length; i++)
			{
				var fileTemplate:File = fileTemplates[i];
				if ( TemplatingHelper.getTemplateLabel(fileTemplate) == eventName )
				{
					var customTemplate:File = getCustomFileFor(fileTemplate);
					if (customTemplate.exists)
					{
						fileTemplate = customTemplate;
					}
						
					createFile(fileTemplate);
					return;
				}
			}
		}
		
		protected function createFile(template:File):void
		{
			var editor:BasicTextEditor = new BasicTextEditor();

			// Let plugins hook in syntax highlighters & other functionality
			var editorEvent:EditorPluginEvent = new EditorPluginEvent(EditorPluginEvent.EVENT_EDITOR_OPEN);
			editorEvent.editor = editor.getEditorComponent();
			editorEvent.newFile = true;
			editorEvent.fileExtension = TemplatingHelper.getExtension(template);
			dispatcher.dispatchEvent(editorEvent);
			
			editor.defaultLabel = "New " + TemplatingHelper.stripTemplate(template.name);
			
			// Read file data
			var fs:FileStream = new FileStream();
			fs.open(template, FileMode.READ);
			var content:String = fs.readUTFBytes(template.size);
			
			// Request additional data for templating
			var event:TemplateEvent = new TemplateEvent(TemplateEvent.REQUEST_ADDITIONAL_DATA, template);
			dispatcher.dispatchEvent(event);
			
			// Replace content if any
			content = TemplatingHelper.replace(content, event.templatingData);
			
			// Set content to editor
			editor.setContent(content);
			
			// Remove empty editor if one is focused
			if (model.activeEditor.isEmpty())
			{
				model.editors.removeItemAt(model.editors.getItemIndex(model.activeEditor));
			}
			
			dispatcher.dispatchEvent(
				new AddTabEvent(editor)
			);
		}
		
		protected function handleNewProjectFile(event:Event):void
		{
			var eventName:String = event.type.substr(27);
			
			// Figure out which menu item was clicked (add extra data var to MenuPlugin/event dispatching?)
			for each (var projectTemplate:File in projectTemplates)
			{
				if ( TemplatingHelper.getTemplateLabel(projectTemplate) == eventName )
				{
					var customTemplate:File = getCustomFileFor(projectTemplate);
					if (customTemplate.exists)
						projectTemplate = customTemplate;
						
					findSettingsFile(projectTemplate);
					break;
				}
			}	
		}
		
		protected function findSettingsFile(projectDir:File):void
		{
			// TODO: If none is found, prompt user for location to save project & template it over
			var files:Array = projectDir.getDirectoryListing();
			
			for each (var file:File in files)
			{
				if (!file.isDirectory)
				{
					if (file.name.indexOf("$Settings.") == 0)
					{
						var ext:String = TemplatingHelper.getExtension(file);
						dispatcher.dispatchEvent(
							new NewProjectEvent(NewProjectEvent.CREATE_NEW_PROJECT, ext, file, projectDir)
						);
						return;
					}
				}
			}
		}
		
		protected function handleTemplateRequest(event:RequestTemplatesEvent):void
		{
			event.fileTemplates = this.fileTemplates;
			event.projectTemplates = this.projectTemplates;	
		}
		
		/*
			Silly little helper methods
		*/
		
		protected function isCustom(template:File):Boolean
		{
			if (template.nativePath.indexOf(File.applicationStorageDirectory.nativePath) == 0)
			{
				return true;
			}
				
			return false;
		}
		
	}
}